Django之Form组件
Form组件,用来做一些数据的提前验证,比如登录注册中,我们定义的是邮箱登录,但是用户却手机号登录,那么用Form组件去实现这个验证过程的话,数据的判断逻辑就不会去数据库匹配啦,这样反而会减轻数据库的压力。
所以,Form组件的功能是对用户的请求数据库做验证的。并且对获取的数据也可以做验证功能。
以往的登录注册中的问题:
在以往的登录注册页面中,如果有输入错误,页面是重新刷新的,所以之前输入对的数据也会随之消失。
重复进行用户数据的校验:正则,长度,是否为空等等。
Form组件的解决方法:
先导入模块:
from django.forms import Formfrom django.forms import fields
后端:
from django.forms import Formfrom django.forms import fieldsclass LoginForm(Form): “““ 字段的名称必须与前端的字典名称一致。 ””” username = fields.CharField( max_length=18, min_length=6, required=True, error_messages={ "required":"用户名不能为空", "min_length":"长度不能小于6位", "max_length":"长度不能大于18位", } ) password = fields.CharField( min_length=16, required=True, error_messages={ "required": "密码不能为空", "min_length": "长度不能小于16位", } )def login(request): if request.method == "GET": return render(request,"login.html") else: obj = LoginForm(request.POST) ret = obj.is_valid() #内部自动校验,ret是False和True的结果 if ret: #用户输入格式正确 print(obj.cleaned_data) #cleaned_data 是一个字典类型,是校验成功后拿到的值。 return redirect('http://www.baidu.com') else: #用户输入格式错误 # print(obj.errors) #所有的错误信息,这个错误信息是一个对象,是 __str__ 方法 # print(obj.errors["username"]) # print(obj.errors["username"][0]) #在LoginForm类中定义了多个条件,那么就可能有多个错误信息,所有可以用索引取第一个 # print(obj.errors["password"]) # print(obj.errors["password"][0]) return render(request,"login.html",{ "obj":obj})
前端:
Title 登录
到目前为止,Form组件的功能是对用户提交的数据进行校验和保留上次输入的内容。
对于前端用户提交的方式,有两种,一个是Form提交,一种是Ajax提交。
其中Form提交是刷新,并失去上次的输入内容。
而Ajax是不刷新,保留上次输入的内容。
Form的验证流程:
当执行is_valid的时候,Form的验证原理是:
1. 获取当期类中所有的字段,也就是LoginForm类中,我们定义的字段。
也就是当每次对LoginForm实例化的时候,会将LoginForm类中的字段放到self.fields中。这个self.fields 类似一个字典。
self.fields = {
"username":fields.CharField(required=Ture), #这个对象包含了这个正则表达式。
"password":fields.CharField(required=Ture), #这个对象包含了这个正则表达式。
}
2. 会循环self.fields,被循环的个数是由LoginForm来定的,因为self.fields循环的就是LoginForm的字段的个数。
for k,v in self.fields.items():
k:k是每次循环的username或password,它是字符串类型。
v:v是对应的正则表达式。
input_value = request.POST.get(k):这个表达式的意思是POST里有很多数据。
这里的k是字段的名字,如何写username,那么就会去找username的数据。
v的正则表达式的值和input_value的值进行校验。
所以在for循环之前,先定义一个flag=Ture.
当v的正则和input_value校验不匹配时,返回False的值。
flag = Turefor k,v in self.fields.items(): input_value = request.POST.get(k) flag = Falsereturn flag
以上的Form流程就是is_valid的原理。
is_valid 是True的话,获取cleaned_data的数据。
Form组件和Ajax的提交验证:
用Ajax的形式将前端的数据发送给后台:
前端代码:
Title 用户登录
后端代码:
def ajax_login(request): obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) else: print(obj.errors) return HttpResponse("...")
这样就算是提交错误了,页面是不会刷新,并且会保留之前输入的数据。
但这里我们实现代码会发现一个问题,那就是在错误提交数据后,也是不刷新啦,前端输入的值后端也拿到了,但是但是,有个问题点,就是前端的页面并没有错误提示呀。
所以,这个ajax的提交并不是那么的那么的完美,所以,我们接下来,就来完善一下这个ajax提交的错误提示。
Form组件和Ajax提交验证的显示错误信息:
这里用到了json.dumps的序列化操作,序列化的是obj.errors的对象。
前端代码:
Title 用户登录
后端代码:
def ajax_login(request): import json ret = { 'status': True,'msg': None} obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) else: # print(obj.errors) # obj.errors对象 ret['status'] = False ret['msg'] = obj.errors v = json.dumps(ret) return HttpResponse(v)
这样就可以实现ajax提交有错误提示啦。
Form组件的常见字段和参数:
在XXXForm类中的字段:
数字类型:
#数字类型number = fields.IntegerField( min_value=10, #这里的min和max不是单纯的数字长度,而是数字范围。 max_value=1000, #有错误信息 error_messages={ "required":"number不能为空", "invalid":"number格式错误,必须是数字", #invalid 格式错误 "min_value":"数值必须大于10", "max_value":"数值必须小于1000", })
邮件类型:
#邮件类型Email = fields.EmailField( #有错误信息 error_messages={ "required":"Email不能为空", "invalid":"Email格式错误,必须是邮件格式", #invalid 格式错误 })
还有对URL的fields.URLField。
还有fields.SlugField 和fields.GenericIPAddressField、fields.DateField、fields.DateTimeField。
但这些内置的也会有不完善的地方,无法达到我们的需求。所以,还有fields.RegesField。
可以用这个fields.RegesField去写正则表达式。
test = fields.RegesField('185\d+')
fields.RegesField是继承CharField的。
而CharField也是继承的Field,所以,我们来看一下Field的里的参数:
Form组件的Field类的参数:
widget 是用来指定生成什么样的HTML标签。比如select,text,input。
但用widget要先导入:from django.forms import widgets
label:写什么就在前端页面显示什么,在前端的写法是:
{ { obj.字段名.label }} 好比: { { obj.t1.label }}
initial:初始值。
在input框中显示默认值用的。
help_text:提供帮助信息:
在Form的字段中定义help_text: help_text='.......'在前端代码和label一样:{ { obj.t1.help_text }}这样就可以在前端显示:.......啦
validators:自定义验证规则
validators=[ ]
localize=False:是否支持本地化,这是用来转化时间的。
disabled=False:是否可以编辑。
label_suffix=None:label内容后缀。
上述这里参数,处理validators以外。其余的一起用,是可以自动生成HTML标签的。
这里就实现一个用后端参数实现一个前端标签的代码:
后端:
class TestForm(Form): t1 = fields.CharField( required=True, label="usename", label_suffix=":", help_text="输入username", disabled=False, initial="username", max_length=8, min_length=2, error_messages={ "required":"不能为空", "max_length":"long", "min_length":"短", } )def login(request): if request.method =="POST": obj = TestForm() return render(request,"login.html",{ "obj":obj}) else: obj = TestForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) else: print(obj.errors) return render(request,"login.html",{ "obj":obj})
前端:
Title 用户登录
以上就是用后端代码实现前端的标签的。
Form小总结:
form有验证的功能:
1.类:
字段 = 正则
2.is_valid()
form有生成HTML标签的功能:
1.类:
字段 = 正则( 正则这里规定一些生成HTML标签的特性)
2.is_valid()
Form组件保留上次输入的内容:
前端:
后端:
class RegiterForm(Form): user = fields.CharField(min_length=8) email = fields.EmailField() password = fields.CharField() phone = fields.RegexField('139\d+')def register(request): if request.method == 'GET': obj = RegiterForm() return render(request,'register.html',{ 'obj':obj}) #这个obj里是没有值的,因为是第一次的请求 else: obj = RegiterForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) else: print(obj.errors) return render(request,'register.html',{ 'obj':obj}) #这里的obj有值,值是input输入的值。
这样输入的值就可以保留。
Form组件小示例班级学生老师管理:
示例中,应用到的Form特性,以后在任何的提交数据和编辑数据中,都应该应用Form组件去完成,因为我们是不信任提交的数据的。
所有,需要用Form去做验证。
后端views代码:
from django.shortcuts import render,redirectfrom app01 import modelsfrom django.forms import Formfrom django.forms import fieldsfrom django.forms import widgetsclass ClassForm(Form): title = fields.RegexField('全栈\d+')def class_list(request): cls_list = models.Classes.objects.all() return render(request,'class_list.html',{ 'cls_list':cls_list})def add_class(request): if request.method == "GET": obj = ClassForm() return render(request,'add_class.html',{ 'obj': obj}) else: obj = ClassForm(request.POST) if obj.is_valid(): # obj.cleaned_data # 字典 # 数据库创建一条数据 # print(obj.cleaned_data) # models.Classes.objects.create(title=obj.cleaned_data['tt']) models.Classes.objects.create(**obj.cleaned_data) return redirect('/class_list/') return render(request,'add_class.html',{ 'obj': obj})def edit_class(request,nid): if request.method == "GET": row = models.Classes.objects.filter(id=nid).first() # 让页面显示初始值 # obj = ClassForm(data={'title': 'asdfasdfasdfas'}) obj = ClassForm(initial={ 'title': row.title}) return render(request,'edit_class.html',{ 'nid': nid,'obj':obj}) else: obj = ClassForm(request.POST) if obj.is_valid(): models.Classes.objects.filter(id=nid).update(**obj.cleaned_data) return redirect('/class_list/') return render(request,'edit_class.html',{ 'nid': nid,'obj':obj})class StudentForm(Form): name = fields.CharField( min_length=2, max_length=6, widget=widgets.TextInput(attrs={ 'class': 'form-control'}) ) email = fields.EmailField(widget=widgets.TextInput(attrs={ 'class': 'form-control'})) age = fields.IntegerField(min_value=18,max_value=25,widget=widgets.TextInput(attrs={ 'class': 'form-control'})) cls_id = fields.IntegerField( # widget=widgets.Select(choices=[(1,'上海'),(2,'北京')]) widget=widgets.Select(choices=models.Classes.objects.values_list('id','title'),attrs={ 'class': 'form-control'}) )def student_list(request): stu_list = models.Student.objects.all() return render(request,'student_list.html',{ 'stu_list':stu_list})def add_student(request): if request.method == "GET": obj = StudentForm() return render(request,'add_student.html',{ 'obj':obj}) else: obj = StudentForm(request.POST) if obj.is_valid(): models.Student.objects.create(**obj.cleaned_data) return redirect('/student_list/') return render(request,'add_student.html',{ 'obj':obj})def edit_student(request,nid): if request.method == "GET": row = models.Student.objects.filter(id=nid).values('name','email','age','cls_id').first() obj = StudentForm(initial=row) return render(request,'edit_student.html',{ 'nid':nid,'obj': obj}) else: obj = StudentForm(request.POST) if obj.is_valid(): models.Student.objects.filter(id=nid).update(**obj.cleaned_data) return redirect('/student_list/') return render(request,'edit_student.html',{ 'nid':nid,'obj': obj})
后端models代码:
from django.db import modelsclass Classes(models.Model): title = models.CharField(max_length=32)class Student(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32) age = models.IntegerField(max_length=32) cls = models.ForeignKey('Classes')class Teacher(models.Model): tname = models.CharField(max_length=32) c2t = models.ManyToManyField('Classes')
URL:
from django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^class_list/', views.class_list), url(r'^add_class/', views.add_class), url(r'^edit_class/(\d+)/', views.edit_class), url(r'^student_list/', views.student_list), url(r'^add_student/', views.add_student), url(r'^edit_student/(\d+)/', views.edit_student),]
前端class_list 代码:
前端add_class 代码:
添加班级
前端edit_class 代码:
编辑班级
前端student_list 代码:
学生列表
添加
- { % for row in stu_list %}
- { { row.name }}-{ { row.email }}-{ { row.age }}-{ { row.cls_id }}-{ { row.cls.title }} 编辑 { % endfor %}
前端add_student 代码:
添加学生
前端edit_student 代码:
老师多对多:
注意:
Select框: 单选 cls_id = fields.IntegerField( # widget=widgets.Select(choices=[(1,'上海'),(2,'北京')]) widget=widgets.Select(choices=models.Classes.objects.values_list('id','title'),attrs={ 'class': 'form-control'}) ) cls_id = fields.ChoiceField( choices=models.Classes.objects.values_list('id','title'), widget=widgets.Select(attrs={ 'class': 'form-control'}) ) obj = FooForm({ 'cls_id':1}) 多选 xx = fields.MultipleChoiceField( choices=models.Classes.objects.values_list('id','title'), widget=widgets.SelectMultiple ) obj = FooForm({ 'cls_id':[1,2,3]})
老师的models 代码:
from django.db import modelsclass Classes(models.Model): title = models.CharField(max_length=32) def __str__(self): return self.titleclass Student(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32) age = models.IntegerField() cls = models.ForeignKey('Classes')class Teacher(models.Model): tname = models.CharField(max_length=32) """ 10 """ c2t = models.ManyToManyField('Classes')
老师的view后端代码:
def teacher_list(request): tea_list = models.Teacher.objects.all() return render(request,'teacher_list.html',{ 'tea_list':tea_list})from django.forms import models as form_modelclass TeacherForm(Form): tname = fields.CharField(min_length=2) # xx = form_model.ModelMultipleChoiceField(queryset=models.Classes.objects.all()) # xx = form_model.ModelChoiceField(queryset=models.Classes.objects.all()) xx = fields.MultipleChoiceField( # choices=models.Classes.objects.values_list('id','title'), widget=widgets.SelectMultiple ) def __init__(self,*args,**kwargs): super(TeacherForm,self).__init__(*args,**kwargs) self.fields['xx'].choices = models.Classes.objects.values_list('id','title')# obj = TeacherForm()# 1. 找到所有字段# 2. self.fields = { # tname: fields.CharField(min_length=2)# }def add_teacher(request): if request.method == "GET": obj = TeacherForm() return render(request,'add_teacher.html',{ 'obj':obj}) else: obj = TeacherForm(request.POST) if obj.is_valid(): xx = obj.cleaned_data.pop('xx') row = models.Teacher.objects.create(**obj.cleaned_data) row.c2t.add(*xx) # [1,2] return redirect('/teacher_list/') return render(request,'add_teacher.html',{ 'obj':obj})def edit_teacher(request,nid): if request.method == "GET": row = models.Teacher.objects.filter(id=nid).first() class_ids = row.c2t.values_list('id') # print(class_ids) # id_list = [] id_list = list(zip(*class_ids))[0] if list(zip(*class_ids)) else [] # obj = TeacherForm(initial={'tname':row.tname,'xx':[1,2,3]}) obj = TeacherForm(initial={ 'tname':row.tname,'xx':id_list}) return render(request,'edit_teacher.html',{ 'obj':obj})
老师的teacher_list 前端代码:
老师列表
ID | 老师姓名 | 任教班级 | 编辑 |
---|---|---|---|
{ { row.id }} | { { row.tname }} | { % for item in row.c2t.all %} { { item }} { % endfor %} | 编辑 |
老师的add_teacher 前端代码:
老师的edit_teacher 前端代码:
编辑老师
Form组件的常用组件:
CheckBox复选框的应用:
test后端:
class TestForm(Form): t1 = fields.CharField( widget=widgets.Textarea(attrs={}) ) t2 = fields.CharField( #单选的 widget=widgets.CheckboxInput ) t3 = fields.MultipleChoiceField( #多选 choices=[(1,'篮球'),(2,'足球'),(3,'溜溜球')], widget=widgets.CheckboxSelectMultiple ) t4 = fields.ChoiceField( #单选,选择互斥 choices=[(1,'篮球'),(2,'足球'),(3,'溜溜球')], widget=widgets.RadioSelect ) t5 = fields.FileField( #上传文件 widget=widgets.FileInput )
def test(request): obj = TestForm(initial={ 't3':[2,3]}) #默认选中 obj.is_valid() return render(request,'test.html',{ 'obj':obj})
test前端:
{ { obj.t2 }} { { obj.t3 }} { { obj.t4 }} { { obj.t5 }}
Form验证执行流程和钩子:
is_valid方法的内部流程:
is_valid的里面有一个return的返回,返回的有self.is_bound 和 self.errors。
这个self.is_bound是只有True和False两个值,在初始化的时候,会进行校验,这个校验的值就是True 和 False。
在self.errors里面有一个self.full_clean方法,这个方法就是去做验证的。这个验证就是循环Form自己的字段,
然后去提交的数据进行校验。
在self.full_clean里面,我们可以看到有cleaned_data,这就是我们的校验数据,这是一个字典类型。
最后是clean_fields处理data的数据。
self._clean_fields() #做cleaned_data数据处理用self._clean_form() #做钩子用self._post_clean()
钩子:是自己定义的,当定义的钩子函数被调用是当每一个字段,自己的正则表达式,自己的函数后执行完,才会执行这个钩子函数。
如果想用钩子函数,那么cleaned_data里是已经有值的啦。
样例:
from django.core.exceptions import ValidationErrorclass TestForm(Form): user = fields.CharField(validators=[]) pwd = fields.CharField() def clean_user(self): v = self.cleaned_data['user'] if models.Student.objects.filter(name=v).count(): raise ValidationError('用户名已经存在') return self.cleaned_data['user'] def clean_pwd(self): return self.cleaned_data['pwd'] def clean(self): #钩子 # user = self.cleaned_data.get('user') # email = self.cleaned_data.get('email') # if models.Student.objects.filter(user=user,email=email).count(): # raise ValidationError('用户名和邮箱联合已经存在') return self.cleaned_data
# def _post_clean(self): # """ # An internal hook for performing additional cleaning after form cleaning # is complete. Used for model validation in model forms. # """ # pass
Form扩展:
字段 = 默认的正则表达式。
在默认的正则中,可以在加额外的正则,用validators=[]
clean_字段 = 必须有返回值。
clean():有返回值,用定义的返回值 cleaned_data = 返回值。
没有返回值,用原来的值,cleaned_data = 原有的值。
Form的验证流程:
1. 写一个Form,用户提供大量的验证数据,先一个一个字段的去获取。
2. 拿一个字段,做自己的正则,当自己的正则执行完,就执行自己的函数。
3. 所有的字段执行完自己函数后,执行clean()方法。
Form组件总结:
1. 使用
class Foo: xx = xxxxxx() # 正则,插件 def clean_xx(): ... def clean(): pass 2. 页面展示 obj = Foo()# 灵活
<form> { {obj.xx}} { {obj.xx}} { {obj.xx}} </form># 简单
{ {obj.as_p}} <ul> { {obj.as_ul}} </ul> <table> { {obj.as_table}} </table>3. 后台
is_valid() clean_data errors
参考blog:XXXXXXXX6144178.html
------- END -------